/**
 * \file Master.cpp
 *
 * Asteriskos Compiler 1
 * Written 2010-2011, by Anthony Berlin and Alexander Carlqvist
 *
 * This file is part of ICC.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "Master.h"
#include "errormanager.h"
#include "Lexer.h"
#include "Parser.h"
#include "semantics.h"
#include "Listorifier.h"

#include <strutil/strutil.h>

#ifdef __WIN32__
    #include <wincon.h>
#endif

#include <cstring>

///
/// \brief Return the filename of this application.
///
string& Master::get_app_filename()
{
    return APP_FILENAME;
}

///
/// \brief Initialize the application.
///
void Master::init_app(const char* title)
{
    // Application info.
    APP_NAME = "Asteriskos Compiler 1";
    APP_ABBR = "AC1";
    APP_VERSION = "0.0.1";
    APP_AUTHOR = "Anthony Berlin & Alex Carlqvist";
    APP_YEAR = "2010-2011";
    APP_TITLE = APP_NAME + " (" + APP_ABBR + ") " + APP_VERSION;

    // Get the name of the executable.
    vector<string> splitVector;

    #ifdef __WIN32__
        if (title[strlen(title) - 4] == '.')
        {
            vector<string> splitVector2;

            split(splitVector, title, boost::is_any_of("."), boost::token_compress_on);
            APP_FILENAME = splitVector[splitVector.size()-2];

            split(splitVector2, APP_FILENAME, boost::is_any_of("\\"), boost::token_compress_on);
            APP_FILENAME = splitVector2[splitVector2.size()-1];
        }
        else
        {
            split(splitVector, title, boost::is_any_of("\\"), boost::token_compress_on);
            APP_FILENAME = splitVector[splitVector.size()-1];
        }
    #endif
    #ifdef __linux__
        split(splitVector, title, boost::is_any_of("/"), boost::token_compress_on);
        APP_FILENAME = splitVector[splitVector.size()-1];
    #endif
    #ifdef __APPLE__
    #endif

    // [Windows]: Set the application title.
    #ifdef __WIN32__
        set_app_title(APP_TITLE);
    #endif

    // Set default param setting.
    p_help = false;
    p_version = false;
    p_noerrors = false;
    p_nowarnings = false;
    p_dump_input = false;
    p_dump_tokens = false;
    p_dump_tree = false;
    p_quiet = false;
    p_output = "";
    p_irlang = "LISTOR";
    p_pedantic = false;
}

///
/// \brief [Windows]: Set the application title.
///
#ifdef __WIN32__
inline void Master::set_app_title(string& title) const
{
    SetConsoleTitle(title.c_str());
}
#endif

///
/// \brief The heart of the program flow.
///
void Master::run(int& argc, char** argv)
{
    GC_INIT();

    unsigned int startms = 0;
    unsigned int timedms = 0;
    unsigned int totalms = 0;
    Tree* ast = NULL_TREE;
    list<Module*> moduleList;
    list<Error*> errors;

    // Init basic stuff for the application.
    init_app(argv[0]);

    // Parse the parameters passed to the application.
    parse_params(argc, argv);

    // Exit application if there were any parameter errors.
    if (m_paramErrorList.size())
    {
        foreach (string& paramError, m_paramErrorList)
            cerr << APP_FILENAME << ": " << paramError << "\n";

        throw new Exception("Aborting because of parameter errors.", 4);
    }

    // Print the usage message, if no arguments were passed to the application.
    if (argc == 1)
    {
        usage();
        return;
    }
    // Print the help message, if the '--help' parameter was used.
    else if (p_help)
    {
        print_help();
        return;
    }
    // Print the application version, if the '--version' parameter was used.
    else if (p_version)
    {
        print_version();
        return;
    }

    // If a translation-unit was specified, load it to memory.
    if (!m_transUnitPath.empty())
    {
        m_transUnit = new TranslationUnit(m_transUnitPath.c_str(), APP_FILENAME);

        if (m_transUnit->get_path()[0] == '\0')
            return;

        if (p_dump_input)
        {
            string temp = APP_ABBR;
            to_lower(APP_ABBR);
            string path = m_transUnitPath + "." + APP_ABBR + "_input";
            APP_ABBR = temp;

            m_transUnit->dump_to_disc(path.c_str(), APP_FILENAME);
        }

        // Set a default output path, if the parameter '-o' was not used.
        if (p_output == "")
        {
            string pathWithoutExt;
            vector<string> splitVector;

            split(splitVector, m_transUnitPath, boost::is_any_of("."), boost::token_compress_on);

            pathWithoutExt =
                m_transUnitPath.substr(
                    0, m_transUnitPath.find(splitVector[splitVector.size()-1])-1);

            to_lower(p_irlang);
            p_output = pathWithoutExt + "." + p_irlang;
            to_upper(p_irlang);
        }
    }
    else
        throw new Exception("Missing input.", 6);

    // Tell the user which file we're processing.
    output((string)"Translation-unit: " + m_transUnit->get_path() + "\n");

    // Create the lexer.
    Lexer* lexer = new Lexer(m_transUnit, &errors);

    // Perform a lexical analysis on the translation-unit.
    output((string)"Performing lexical analysis ...");

    startms = get_ticks();
    lexer->analyse();

    #if __WIN32__
        timedms = get_milli_span(startms);
        totalms = timedms;
    #else
        timedms = get_ticks();
        totalms = timedms - startms;
    #endif

    output((string)" Done (" + boost::lexical_cast<string>(timedms) + "ms)\n");

    if (p_dump_tokens)
        lexer->dump_to_disc(APP_FILENAME);

    SymbolTable* globalScope = NULL;
    Parser* parser = NULL;

    if (!found_errors(errors))
    {
        output((string)"Parsing token stream ...");

        // Create the parser.
        parser = new Parser(lexer, &errors, "");
        semantics::set_parser(parser);

        // Create the global symbol table for this translation unit.
        globalScope = new SymbolTable;

        startms = get_ticks();

        // Parse the token stream produced by the lexer, and produce an abstraxt syntax tree.
        ast = parser->parse_file(globalScope, moduleList);

        #if __WIN32__
            timedms = get_milli_span(startms);
            totalms += timedms;
        #else
            timedms = get_ticks();
            totalms += timedms - startms;
        #endif

        output((string)" Done (" + boost::lexical_cast<string>(timedms) + "ms)\n");

        if (p_dump_tree)
        {
            string dump(m_transUnitPath + ".dump_tree");
            std::ofstream dumpFileStream(dump.c_str());
            ast->dump_to_disc(dumpFileStream);
            dumpFileStream.close();
        }
    }

    if (!found_errors(errors))
    {
        output((string)"Generating IR code (lang: " + p_irlang + ") ...");

        startms = get_ticks();

        // Convert the abstraxt syntax tree to a LISTOR tree and generate LISTOR code.
        if (p_irlang == "LISTOR")
        {
            Listorifier listorifier(moduleList, ast, globalScope, parser->m_mainDeclTree);
            listorifier.rearrange_ast();
            listorifier.generate_code(listorifier.get_listor_tree());
            listorifier.output_code(p_output);
        }

        #if __WIN32__
            timedms = get_milli_span(startms);
            totalms += timedms;
        #else
            timedms = get_ticks();
            totalms += timedms - startms;
        #endif

        output((string)" Done (" + boost::lexical_cast<string>(timedms) + "ms)\n");
        output((string)"Output: " + p_output + "\n");
    }

    // Print total elapsed time, in seconds.
    string totalms_str = boost::lexical_cast<string>((double)totalms / 1000.0);
    vector<string> tokens;

    split(tokens, totalms_str, boost::is_any_of("."), boost::token_compress_on);

    string seconds = tokens[0];
    string decimals = "0";

    if (tokens.size() > 1)
    {
        decimals = tokens[1].substr(0, 3);
        totalms_str = seconds + "." + decimals;
    }
    else
        totalms_str = seconds;

    output((string)"Total elapsed time: " + totalms_str + "s\n");

    // Report any potential warnings and/or errors.
    report_errors(errors, found_errors(errors));

    // Clean up.
    semantics::parser = NULL;
}

///
/// \brief Check to see if we have found any errors during the lexical analysis or parsing phases.
/// \return \c true if errors were found (unless they are warnings).
///
bool Master::found_errors(list<Error*>& errors) const
{
    foreach (Error* error, errors)
        if (error->m_isError)
            return true;

    return false;
}

///
/// \brief Dump all errors found in the linked list to disc.
///
void Master::report_errors(list<Error*>& errors, bool printAbortMessage = true) const
{
    if (printAbortMessage)
        output("Process aborted.\n");

    foreach (Error* error, errors)
    {
        if ((error->m_isError && p_noerrors) || (!error->m_isError && p_nowarnings))
            continue;

        if (error->m_isError)
            errormanager::write_error(error);
        else
            errormanager::write_error((Warning*)error);
    }
}

///
/// \brief Print the help message.
///
inline void Master::print_help() const
{
    usage();

    write_console("Options:");
    write_console("  --help                   Display this information.");
    write_console("  --version                Display the version of the application.");
    write_console("  -o <filename>            Specifiy the output filename.");
    write_console("  -noerrors                Don't report any errors.");
    write_console("  -nowarnings              Don't report any warnings.");
    write_console("  -dump-input              Dump the input to disc.");
    write_console("  -dump-tokens             Dump the token stream from the lexer to disc.");
    write_console("  -dump-tree               Dump the abstract syntax tree that the parser");
    write_console("                           produces to disc.");
    write_console("  -quiet                   Do not print anything to the screen, except for");
    write_console("                           warnings and errors, if '-print-errors' is used.");
    write_console("                           Debugging information will be printed as well.");
    write_console("  -irlang <lang>           Select IR langauge to output: LISTOR, BIRL.");
    write_console("  -pedantic                Issue pedantic warnings.");
    write_console("");
    write_console("Note: One dash, proceed with execution, two dashes, exit immediately.");
}

///
/// \brief Print the application version.
///
inline void Master::print_version() const
{
    cout << APP_TITLE << ", " << "written by " << APP_AUTHOR << ".\n";
	cout << "Copyright (C) " << APP_YEAR << " Free Software Foundation, Inc.\n";
	cout << "This is free software; see the source for copying conditions. There is NO\n" <<
       "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n";
}

///
/// \brief Print the usage message.
///
inline void Master::usage() const
{
    cout << "Usage: " << APP_FILENAME << " [options] file\n";
}

///
/// \brief Parse the parameters passed to the application, and configure it.
///
void Master::parse_params(int& argc, char** argv)
{
    int n = 1;

    while (n < argc)
    {
        if (argv[n][0] == '-')
        {
            if (!strcmp(argv[n], "-o"))
            {
                if (++n >= argc)
                    missing_arg(--n, argv);

                p_output = argv[n];
            }
            else if (!strcmp(argv[n], "-noerrors"))
                p_noerrors = true;
            else if (!strcmp(argv[n], "-nowarnings"))
                p_nowarnings = true;
            else if (!strcmp(argv[n], "-dump-input"))
                p_dump_input = true;
            else if (!strcmp(argv[n], "-dump-tokens"))
                p_dump_tokens = true;
            else if (!strcmp(argv[n], "-dump-tree"))
                p_dump_tree = true;
            else if (!strcmp(argv[n], "-quiet") || !strcmp(argv[n], "-q"))
                p_quiet = true;
            else if (!strcmp(argv[n], "--help"))
                p_help = true;
            else if (!strcmp(argv[n], "--version") || !strcmp(argv[n], "--ver"))
                p_version = true;
            else if (!strcmp(argv[n], "-irlang"))
            {
                if (++n >= argc)
                    missing_arg(--n, argv);
                else
                {
                    p_irlang = argv[n];
                    to_upper(p_irlang);

                    if (p_irlang != "LISTOR" && p_irlang != "BIRL")
                    {
                        string msg = (string)"Unknown IR langauge '" + p_irlang + "' selected.";
                        push_param_error(msg);
                    }
                    if (p_irlang == "BIRL")
                        push_param_error("IR language 'BIRL' is currently not implemented.");
                }
            }
            else if (!strcmp(argv[n], "-pedantic"))
                p_pedantic = true;
            else
                unrecognized_option(n, argv);
        }
        else
        {
            if (m_transUnitPath.empty())
                m_transUnitPath = argv[n];
            else
                push_param_error("Too many translation units.");
        }

        ++n;
    }
}

///
/// \brief Add a parameter error about in the form of "Unrecognized option 'option'.".
///
inline void Master::unrecognized_option(int& arg, char** argv)
{
    string msg = (string)"Unrecognized option '" + argv[arg] + "'.";
    push_param_error(msg);
}

///
/// \brief Add a parameter error about in the form of "Argument to 'option' is missing.".
///
inline void Master::missing_arg(int& arg, char** argv)
{
    string msg = (string)"Argument to '" + argv[arg] + "' is missing.";
    push_param_error(msg);
}

///
/// \brief Add a parameter error to 'm_paramErrorList'.
///
inline void Master::push_param_error(string& errMsg)
{
    m_paramErrorList.push_back(errMsg);
}

void Master::push_param_error(const char* errMsg)
{
    string msg = errMsg;
    m_paramErrorList.push_back(msg);
}

///
/// \brief Output a string to the screan if the option '-quiet' is not used.
///
template <class T>
void Master::output(T msg) const
{
    if (!p_quiet)
        cout << msg;
}

///
/// Write something to the console. It makes sure the string never includes a line that is longer
/// than 79 characters. If it does, an exception is thrown. It always ends with a new-line.
///
void Master::write_console(const char* msg) const
{
    string str = msg;
    write_console(str);
}

void Master::write_console(string& msg) const
{
    vector<string> tokens;

    split(tokens, msg, boost::is_any_of("\n"), boost::token_compress_on);

    foreach (string token, tokens)
    {
        if (token.length() >= 80)
        {
            string exMsg = (string)"Exception: String too long for console window. Length: " +
                boost::lexical_cast<string>(token.length())+". Max: 79.";

            throw new Exception(exMsg.c_str(), 5);
        }
    }

    cout << msg << "\n";
}
